#pragma once
//设计3.2.1原则的1超市
#include<iostream>
#include<queue>
#include<unistd.h>
#include<pthread.h>
#include"Cond.hpp"
#include"Mutex.hpp"

namespace BlockQueuemode
{
    //v2版本--使用直接封装的锁和条件变量
    using namespace condmod;
    using namespace LockMutex;

    static int gcap = 10;
    template<class T>
    class BlockQueue
    {
    private:
        //容器是否满了
        bool full()
        {
            return _q.size() == _cap;
        }
        //容器是否为空
        bool Empty()
        {
            return _q.empty();
        }
    public:
        BlockQueue(int cap = gcap):_cap(cap)
        {}
        ~BlockQueue()
        {}
        //入队列
        void Equeue(const T& data)//生产者 
        {
            mutex_lock lock(_mutex);
            //你想放数据，就能放数据吗？？生产数据是有条件的！
            //结论1：在临界区中等待是必然的（目前）

            //为什么是while循环，下面有讲
            while(full())
            {
                //等待
                //在临界区等待是必然的
                //1.也就是说，等待的时候，必定是持有锁的，持有锁是有问题的
                //2.未来消费者也要申请锁，也就是说生产者一直等，消费者无法申请锁就无法消费
                //所以这个函数提供了第二个参数

                //第二个参数是，等待时会释放锁
                _1_num++;
                _cond1.Wait(_mutex);  //消费者还没来得及解锁，这个就会在锁上等
                //当返回，线程被唤醒就会自动重新申请并持有锁(因为它会在临界区内醒来！)
                _1_num--;
            }
            //走到这里要么if条件不满足，要么线程被唤醒

            //没有满，才入队列
            _q.push(data);

            //有消费者线程等待-就唤醒
            if(_2_num)
            {
                _cond1.Notify();
            }
            
        }
        //出队列-带出去
        void Pop(T* out)//消费者
        {
            mutex_lock lock(_mutex);

            //如果生产者生产了1个又唤醒了全部消费者线程，就会导致第1个线程消费完没有了，其他线程还能继续消费
            //这种情况就叫伪唤醒，所以最好加个while循环来判断
            //不为空就是0，当消费完队列没有数据判断就是1，没有数据其他线程就会继续等待
            while(Empty())
            {
                _2_num++;
               _cond2.Wait(_mutex);//等待
                //当返回，线程被唤醒就会自动重新申请并持有锁(因为它会在临界区内醒来！)
                _2_num--;
            }
            //走到这里要么if条件不满足，要么线程被唤醒

           *out = _q. front();
           _q.pop();
           //有生产者线程等待-就唤醒
           if(_1_num)
           {
                _cond2.Notify();
           }
        }
    private:
        std::queue<T> _q;       //保存数据的容器-临界资源
        int _cap;               //阻塞队列的最大容量
        Mutex _mutex; //互斥-定义锁

        Cond _cond1;  //条件变量-生产者
        Cond _cond2;  //条件变量-消费者

        //线程等待的个数
        int _1_num;//生产者等待个数
        int _2_num;//消费者等待个数
    };


    // //v1版本
    // static int gcap = 10;
    // template<class T>
    // class BlockQueue
    // {
    // private:
    //     //容器是否满了
    //     bool full()
    //     {
    //         return _q.size() == _cap;
    //     }
    //     //容器是否为空
    //     bool Empty()
    //     {
    //         return _q.empty();
    //     }
    // public:
    //     BlockQueue(int cap = gcap):_cap(cap)
    //     {   
    //         pthread_mutex_init(&_mutex,nullptr);//初始化锁
    //         pthread_cond_init(&_cond1,nullptr); //初始化条件变量
    //         pthread_cond_init(&_cond2,nullptr); //初始化条件变量
    //     }
    //     ~BlockQueue()
    //     {
    //         pthread_mutex_destroy(&_mutex);//释放锁
    //         //释放条件变量
    //         pthread_cond_destroy(&_cond1);
    //         pthread_cond_destroy(&_cond2);
    //     }
    //     //入队列
    //     void Equeue(const T& data)//生产者
    //     {
    //         pthread_mutex_lock(&_mutex);
    //         //你想放数据，就能放数据吗？？生产数据是有条件的！
    //         //结论1：在临界区中等待是必然的（目前）

    //         //为什么是while循环，下面有讲
    //         while(full())
    //         {
    //             //等待
    //             //在临界区等待是必然的
    //             //1.也就是说，等待的时候，必定是持有锁的，持有锁是有问题的
    //             //2.未来消费者也要申请锁，也就是说生产者一直等，消费者无法申请锁就无法消费
    //             //所以这个函数提供了第二个参数

    //             //第二个参数是，等待时会释放锁
    //             _1_num++;
    //             pthread_cond_wait(&_cond1,&_mutex);//消费者还没来得及解锁，这个就会在锁上等
    //             //当返回，线程被唤醒就会自动重新申请并持有锁(因为它会在临界区内醒来！)
    //             _1_num--;
    //         }
    //         //走到这里要么if条件不满足，要么线程被唤醒

    //         //没有满，才入队列
    //         _q.push(data);

    //         //有消费者线程等待-就唤醒
    //         if(_2_num)
    //         {
    //             pthread_cond_signal(&_cond2);
    //         }
    //         pthread_mutex_unlock(&_mutex);
    //     }
    //     //出队列-带出去
    //     void Pop(T* out)//消费者
    //     {
    //         pthread_mutex_lock(&_mutex);

    //         //如果生产者生产了1个又唤醒了全部消费者线程，就会导致第1个线程消费完没有了，其他线程还能继续消费
    //         //这种情况就叫伪唤醒，所以最好加个while循环来判断
    //         //不为空就是0，当消费完队列没有数据判断就是1，没有数据其他线程就会继续等待
    //         while(Empty())
    //         {
    //             _2_num++;
    //             pthread_cond_wait(&_cond2,&_mutex);//等待
    //             //当返回，线程被唤醒就会自动重新申请并持有锁(因为它会在临界区内醒来！)
    //             _2_num--;
    //         }
    //         //走到这里要么if条件不满足，要么线程被唤醒

    //        *out = _q. front();
    //        _q.pop();
    //        //有生产者线程等待-就唤醒
    //        if(_1_num)
    //        {
    //             pthread_cond_signal(&_cond1);
    //        }
    //        pthread_mutex_unlock(&_mutex);
    //     }
    // private:
    //     std::queue<T> _q;       //保存数据的容器-临界资源
    //     int _cap;               //阻塞队列的最大容量
    //     pthread_mutex_t _mutex; //互斥-定义锁

    //     pthread_cond_t _cond1;  //条件变量-生产者
    //     pthread_cond_t _cond2;  //条件变量-消费者

    //     //线程等待的个数
    //     int _1_num;//生产者等待个数
    //     int _2_num;//消费者等待个数
    // };
}